﻿using System;
using System.Collections.Generic;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;

namespace Bookvar.Console
{
    internal abstract class DynamicLanguageEngine : ILanguageEngine
    {
        private ScriptModule currentModule;
        private DynamicLanguageCodeSenseProvider codeSenseProvider;

        public abstract string LanguageName{ get; }

        public abstract string LanguageShortName{ get; }

        #region ILanguageEngine Members

        public abstract ScriptEngine ScriptEngine { get; }

        public virtual bool IsExpressionValid( string expression )
        {
            if ( string.IsNullOrEmpty( expression ) )
            {
                return false;
            }
            ErrorSink sink = new ErrorSink();
            CompilerOptions options = ScriptEngine.GetDefaultCompilerOptions();
            SourceCodeUnit codeUnit = new SourceCodeUnit( ScriptEngine, expression );
            CompilerContext context = new CompilerContext( codeUnit, options, sink );

            ScriptEngine.Compiler.ParseExpressionCode( context );
            return !sink.AnyError;
        }

        public bool IsStatementComplete( string statement )
        {
            try
            {
                bool allowIncomplete = !( statement == null || statement.Trim().Length == 0 );
                InteractiveCodeProperties props = ScriptEngine.GetInteractiveCodeProperties( statement );
                return InteractiveCodePropertiesEnum.IsValidAndComplete( props, allowIncomplete );
            }
            catch ( Exception )
            {
                return true;
            }
        }

        public Version Version
        {
            get { return new Version( ScriptEngine.VersionString ); }
        }

        public ScriptModule CurrentModule
        {
            get
            {
                if (  currentModule == null )
                {
                    currentModule = ScriptDomainManager.CurrentManager.CreateModule( "__main__" );
                    ScriptDomainManager.CurrentManager.PublishModule( currentModule, "__main__" );
                }
                return currentModule;
            }
        }

        public ICodeSenseProvider CodeSenseProvider
        {
            get
            {
                if (codeSenseProvider == null)
                {
                    codeSenseProvider = new DynamicLanguageCodeSenseProvider( ScriptEngine, CurrentModule );
                }
                return codeSenseProvider;
            }
        }

        #endregion

        
        private class DynamicLanguageCodeSenseProvider : CodeSenseProvider
        {
            public DynamicLanguageCodeSenseProvider( IScriptEngine engine, IScriptModule module ) : base( engine, module )
            {
            }

            public override string GetFunctionSignature( string name )
            {
                if ( !string.IsNullOrEmpty( name ) )
                {
                    object obj = GetCurrentObject( name );
                    if (obj != null && Engine.IsObjectCallable(obj, Module))
                    {
                        return Engine.GetObjectDocumentation( obj );
                    }
                }

                return null;
            }

            public override string[] GetMemberNames( string name )
            {
                if (!string.IsNullOrEmpty(name))
                {
                    object obj = GetCurrentObject( name );
                    if ( obj != null )
                    {
                        return Engine.GetObjectMemberNames( obj );
                    }
                }

                return new string[0];
            }

            public override string GetFunctionDoc( string name )
            {
                throw new NotImplementedException();
            }

            private object GetCurrentObject( string name )
            {
                SourceCodeUnit codeUnit = new SourceCodeUnit( Engine, name );
                //TODO: cache the tokenCategorizer
                ITokenCategorizer tokenCategorizer = Engine.LanguageProvider.GetTokenCategorizer();
                tokenCategorizer.Initialize( null, codeUnit.GetReader(), new SourceLocation( 0, 1, 1 ) );
                List<TokenInfo> tokens = new List<TokenInfo>( tokenCategorizer.ReadTokens( name.Length ) );
                if ( tokens[ 0 ].Category == TokenCategory.Identifier )
                {
                    TokenInfo token = tokens[ 0 ];
                    tokens.RemoveAt( 0 );
                    object currentObject;
                    if ( Engine.TryGetVariable( name.Substring( 0, token.SourceSpan.End.Index ), Module, out currentObject ) )
                    {
                        while ( tokens.Count > 0 )
                        {
                            token = tokens[ 0 ];
                            tokens.RemoveAt( 0 );
                            string tokenText = name.Substring( token.SourceSpan.Start.Index,
                                                               token.SourceSpan.End.Index - token.SourceSpan.Start.Index );
                            if ( token.Category == TokenCategory.Identifier )
                            {
                                object memberValue;
                                if ( Engine.TryGetObjectMemberValue( currentObject, tokenText, Module, out memberValue ) )
                                {
                                    currentObject = memberValue;
                                }
                                else
                                {
                                    return null;
                                }
                            }
                            else if ( token.Category == TokenCategory.EndOfStream )
                            {
                                return currentObject;
                            }
                            else if ( token.Category == TokenCategory.WhiteSpace )
                            {
                                //The Python token categorizer returns an extra whitespace token at the end of single line statements
                                continue;
                            }
                            else if ( token.Trigger == TokenTriggers.MemberSelect )
                            {
                                continue;
                            }
                            else
                            {
                                return null;
                            }
                        }
                        return currentObject;
                    }
                }

                return null;
            }
        }
    }
}